package dbexp.connection;

import static dbexp.utils.Error.access_property;
import static dbexp.utils.Error.sql;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import dbexp.connection.model.ConnectionInfo;
import dbexp.utils.DbProperties;

public final class PoolManager<P> {
	private static class PoolItem<P> {
		boolean	inUse	= false;
		P		item;
		
		PoolItem(final P item) {
			this.item = item;
		}
	}
	
	private final List<PoolItem<P>>	items	= new ArrayList<PoolItem<P>>();
	
	public void add(final P con) {
		items.add(new PoolItem<P>(con));
	}
	
	public P get() {
		for (PoolItem<P> pitem : items) {
			if (pitem.inUse == false) {
				pitem.inUse = true;
				return pitem.item;
			}
		}
		return null;
	}
	
	public void release(P tor) {
		for (PoolItem<P> pi : items) {
			if (tor == pi.item) {
				pi.inUse = false;
				return;
			}
		}
	}
	public int getSize(){
		return items.size();
	}
	public boolean isInUse() {
		for (int i = 0; i < items.size(); i++) {
			PoolItem<P> pitem = (PoolItem<P>) items.get(i);
			if (pitem.inUse) {
				return pitem.inUse;
			}
		}
		return false;
		
	}
	
	public boolean isEmpty() {
		return items.size() == 0;
	}
	
	private static PoolManager						driverManager		= new PoolManager();
	private final Map<ConnectionInfo, Connection>	pooledConnections	= new HashMap<ConnectionInfo, Connection>();
	private boolean									lastClose			= false;
	
	public PoolManager() {

	}
	
	public static PoolManager getInstance() {
		return driverManager;
	}
	
	public boolean closeCurrentConnection(final ConnectionInfo connectionInfo) {
		final Object connectionFromPool = this.pooledConnections
				.get(connectionInfo);
		if (connectionFromPool != null) {
			final Connection connection = (Connection) connectionFromPool;
			try {
				if (!connection.isClosed()) {
					connection.close();
				}
				this.pooledConnections.remove(connectionInfo);
			}
			catch (final Exception e) {
				return false;
			}
		} else {
			return false;
		}
		return true;
	}
	
	public Connection getConnection(final ConnectionInfo connectionInfo)
			throws PoolManagerException, DbStateException, DbAccessException
	{
		Connection connection = null;
		final Object connectionFromPool = this.pooledConnections
				.get(connectionInfo);
		if (connectionFromPool == null) {
			try {
				final Class<?> driverClass = Class.forName(DbProperties
						.getInstance().getDrivertName());
				final Driver driver = (Driver) driverClass.newInstance();
				System.out.println(connectionInfo.getPassword());
				final String passwordInClearText = connectionInfo.getPassword();
				final Properties props = new Properties();
				props.put("user", connectionInfo.getUsername());
				props.put("password", passwordInClearText);
				
				connection = driver.connect(connectionInfo.getDatabaseURL(),
						props);
				if (connection == null) {
					throw new DbBadPropertiesAccessException(
							access_property.format(DbProperties.getInstance()
									.getDrivertName(), connectionInfo
									.getDatabaseURL()));
				}
				this.pooledConnections.put(connectionInfo, connection);
			}
			catch (final ClassNotFoundException exc) {
				throw new PoolManagerException(
						"Nie można załadować sterownika bazy danych.("
								+ exc.getMessage() + ")");
			}
			catch (final InstantiationException exc) {
				throw new PoolManagerException(
						"Nie można utworzyć instancji sterownika bazy danych.("
								+ exc.getMessage() + ")");
			}
			catch (final IllegalAccessException exc) {
				throw new PoolManagerException(
						"Brak dostępu do instancji sterownika bazy danych.("
								+ exc.getMessage() + ")");
			}
			catch (final SQLException exc) {
				System.out.println(sql.state(exc));
				sql.throwDbStateException(exc);
			}
			catch (final DbBadPropertiesAccessException e) {
				throw new PoolManagerException(
						"Błędny sposób połaczenia ze sterownikiem:"
								+ DbProperties.getInstance().getDrivertName()
								+ " z użyciem adresu:"
								+ connectionInfo.getDatabaseURL());
			}
		} else {
			connection = (Connection) connectionFromPool;
			try {
				if ((connection != null) && connection.isClosed() && !lastClose)
				{
					lastClose = true;
					connection = null;
					this.pooledConnections.remove(connectionInfo);
					connection = getConnection(connectionInfo);
				}
			}
			catch (final SQLException sqle) {
				throw new DbStateException("Błąd sprawdzania stanu połączenia");
			}
		}
		return connection;
	}
	
	/**
	 * Propsy do oracla
	 * - internal_logon String The value of this property is used as the user
	 * name when performing an internal logon. Usually this will be SYS or
	 * SYSDBA.
	 * 
	 * - defaultRowPrefetch int The value of this property is used as the
	 * default number of rows to prefetch.
	 * 
	 * - defaultExecuteBatch int The value of this property is used as the
	 * default batch size when using Oracle style batching.
	 * 
	 * - processEscapes boolean If the value of this property is "false" then
	 * the default setting for Statement.setEscapeProccessing is false.
	 * 
	 * - disableDefineColumnType boolean When this connection property has the
	 * value true, the method defineColumnType is has no effect. This is highly
	 * recommended when using the Thin driver, especially when the database
	 * character set contains four byte characters that expand to two UCS2
	 * surrogate characters, e.g. AL32UTF8. The method defineColumnType provides
	 * no performance benefit (or any other benefit) when used with the 10.x.x
	 * Thin driver. This property is provided so that you do not have to remove
	 * the calls from your code. This is especially valuable if you use the same
	 * code with Thin driver and either the OCI or Server Internal driver.
	 * 
	 * - DMSName String Set the name of the DMS Noun that is the parent of all
	 * JDBC DMS metrics.
	 * 
	 * - DMSType String Set the type of the DMS Noun that is the parent of all
	 * JDBC DMS metrics.
	 * 
	 * - AccumulateBatchResult boolean When using Oracle style batching, JDBC
	 * determines when to flush a batch to the database. If this property is
	 * true, then the number of modified rows accumulated across all batches
	 * flushed from a single statement. The default is to count each batch
	 * separately.
	 * 
	 * - oracle.jdbc.J2EE13Compliant boolean If the value of this property is
	 * "true", JDBC uses strict compliance for some edge cases. In general
	 * Oracle's JDBC drivers will allow some operations that are not permitted
	 * in the strict interpretation of J2EE 1.3. Setting this property to true
	 * will cause those cases to throw SQLExceptions. There are some other edge
	 * cases where Oracle's JDBC drivers have slightly different behavior than
	 * defined in J2EE 1.3. This results from Oracle having defined the behavior
	 * prior to the J2EE 1.3 specification and the resultant need for
	 * compatibility with existing customer code. Setting this property will
	 * result in full J2EE 1.3 compliance at the cost of incompatibility with
	 * some customer code. Can be either a system property or a connection
	 * property.
	 * The default value of this property is "false" in classes12.jar and
	 * ojdbc14.jar. The default value is "true" in classes12dms.jar and
	 * ojdbc14dms.jar. It is true in the dms jars because they are used
	 * exclusively in Oracle Application Server and so J2EE compatibility is
	 * more important than compatibility with previous Oracle versions.
	 * 
	 * - oracle.jdbc.TcpNoDelay boolean If the value of this property is "true",
	 * the TCP_NODELAY property is set on the socket when using the Thin driver.
	 * See java.net.SocketOptions.TCP_NODELAY. Can be either a system property
	 * or a connection property.
	 * 
	 * - defaultNChar boolean If the value of this property is "true", the
	 * default mode for all character data columns will be NCHAR.
	 * 
	 * - useFetchSizeWithLongColumn boolean If the value of this property is
	 * "true", then JDBC will prefetch rows even though there is a LONG or LONG
	 * RAW column in the result. By default JDBC fetches only one row at a time
	 * if there are LONG or LONG RAW columns in the result. Setting this
	 * property to true can improve performance but can also cause SQLExceptions
	 * if the results are too big.
	 * 
	 * - remarksReporting boolean If the value of this property is "true",
	 * OracleDatabaseMetaData will include remarks in the metadata. This can
	 * result in a substantial reduction in performance.
	 * 
	 * - includeSynonyms boolean If the value of this property is "true", JDBC
	 * will include synonyms when getting information about a column.
	 * 
	 * - restrictGetTables boolean If the value of this property is "true", JDBC
	 * will return a more refined value for DatabaseMetaData.getTables.
	 * By default JDBC will return things that are not accessible tables. These
	 * can be non-table objects or accessible synonymns for inaccessible tables.
	 * If
	 * this property is true JDBC will return only accessible tables. This has a
	 * substantial performance penalty.
	 * 
	 * - fixedString boolean If the value of this property is "true", JDBC will
	 * use FIXED CHAR semantic when setObject is called with a String argument.
	 * By default JDBC uses VARCHAR semantics. The difference is in blank
	 * padding. With the default there is no blank padding so, for example, 'a'
	 * does not equal 'a ' in a CHAR(4). If true these two will be equal.
	 * 
	 * - oracle.jdbc.ocinativelibrary String Set the name of the native library
	 * for the oci driver. If not set, the default name, libocijdbcX (X is a
	 * version number), is used.
	 * 
	 * - SetBigStringTryClob boolean Setting this property to "true" forces*
	 * PreparedStatement.setString method to use setStringForClob if the data is
	 * larger than 32765 bytes. Please note that using this method with VARCHAR
	 * and LONG columns may cause large data to be truncated silently, or cause
	 * other errors differing from the normal behavior of setString.
	 * 
	 * - oracle.jdbc.StreamBufferSize int Set size of the buffer for the
	 * InputStream/Reader obtained from getXXXStream/getXXXReader. The default
	 * size is 16k. The size passed should be at least 4096 or else 4096 is
	 * assumed.
	 * 
	 * - OCINewPassword String Pass the value of new password to be set during
	 * logon. This could be typically used for resetting the password when the
	 * password has expired or when the account is in the grace period.
	 * 
	 * - oracle.jdbc.RetainV9BehaviorForLongBind boolean This is applicable only
	 * for the thin driver.
	 * Pass true to retain the V9 bind behavior for Long and potential long
	 * binds.
	 * false is the default behavior which would emulate the same behavior as in
	 * OCI driver.
	 * 
	 * - oracle.jdbc.FreeMemoryOnEnterImplicitCache boolean Clear the define
	 * buffers before caching the statement when Implicit statement caching is
	 * enabled. Setting the value to true would enable the clearing of define
	 * buffers before caching of Statements in the Statement cache. false is the
	 * default
	 * value and this would behave in the same way as statement caching worked
	 * in prior releases.
	 * 
	 * - oracle.jdbc.ReadTimeout int Read timeout while reading from the socket.
	 * This affects thin driver only. Timeout is in milliseconds.
	 * 
	 * - oracle.net.encryption_client string static REJECTED ACCEPTED REQUESTED
	 * REQUIRED
	 * 
	 * - oracle.net.encryption_types_client string static RC4_40 RC4_56 DES40C
	 * DES56C 3DES112 3DES168
	 * 
	 * - oracle.net.crypto_checksum_client string static REJECTED ACCEPTED
	 * REQUESTED REQUIRED
	 * 
	 * - oracle.net.crypto_checksum_types_client string static MD5
	 * oracle.net.ssl_cipher_suites
	 * "(ssl_rsa_export_with_rc4_40_md5, ssl_rsa_export_with_des40_cbc_sha)";
	 * "oracle.net.ssl_client_authentication", "false";
	 * "oracle.net.ssl_version", "3.0"; "oracle.net.encryption_client",
	 * "REJECTED"; "oracle.net.crypto_checksum_client", "REJECTED"
	 * posgres: props.setProperty("ssl","true");
	 * user = String The database user on whose behalf the connection is being
	 * made.
	 * password = String The database user's password.
	 * ssl Connect using SSL. The driver must have been compiled with SSL
	 * support. This property does not need a value associated with it. The mere
	 * presence of it specifies a SSL connection. However, for compatibility
	 * with future versions, the value "true" is preferred. For more information
	 * see Chapter 4, Using SSL.
	 * 
	 * - sslfactory = String The provided value is a class name to use as the*
	 * SSLSocketFactory when establishing a SSL connection. For more information
	 * see the section called “Custom SSLSocketFactory”.
	 * 
	 * - sslfactoryarg = String This value is an optional argument to the
	 * constructor of the sslfactory class provided above. For more information
	 * see the section called “Custom SSLSocketFactory”.
	 * 
	 * - compatible = String Act like an older version of the driver to retain
	 * compatibility with older applications. At the moment this controls two
	 * driver behaviors, the connection protocol and the handling of binary data
	 * fields.
	 * Older versions of the driver used this property to also control the
	 * protocol used to connect to the backend. This is now controlled by the
	 * protocolVersion property.
	 * Information on binary data handling is detailed in Chapter 7, Storing
	 * Binary Data. To force the use of Large Objects set the compatible
	 * property to 7.1.
	 * 
	 * - protocolVersion = String The driver supports both the V2 and V3
	 * frontend/backend protocols. The V3 protocol was introduced in 7.4 and the
	 * driver will by default try to connect using the V3 protocol, if that
	 * fails it will fall back to the V2 protocol. If the protocolVersion
	 * property is specified, the driver will try only the specified protocol
	 * (which should be either "2" or "3"). Setting protocolVersion to "2" may
	 * be used to avoid the failed attempt to use the V3 protocol when*
	 * connecting to a version 7.3 or earlier server, or to force the driver to
	 * use the V2 protocol despite connecting to a 7.4 or greater server.
	 * 
	 * - loglevel = int Set the amount of logging information printed to the
	 * DriverManager's current value for LogStream or LogWriter. It currently
	 * supports values of org.postgresql.Driver.DEBUG (2) and
	 * org.postgresql.Driver.INFO (1). INFO will log very little information
	 * while DEBUG will produce significant detail. This property is only really
	 * useful if you are a developer or are having problems with the driver.
	 * 
	 * - charSet = String The character set to use for data sent to the database
	 * or recieved from the database. This property is only relevent for server
	 * versions less than or equal to 7.2. The 7.3 release was the first with
	 * multibyte support compiled by default and the driver uses its character
	 * set translation facilities instead of trying to do it itself.
	 * 
	 * - allowEncodingChanges = boolean
	 * When using the V3 protocol the driver monitors changes in certain server
	 * configuration parameters that should not be touched by end users. The
	 * client_encoding setting is set by the driver and should not be altered.
	 * If the driver detects a change it will abort the connection. There is one
	 * legitimate exception to this behavior though, using the COPY command on a
	 * file residing on the server's filesystem. The only means of specifying
	 * the encoding of this file is by altering the client_encoding setting. The
	 * JDBC team considers this a failing of the COPY command and hopes to
	 * provide an alternate means of specifying the encoding in the future, but
	 * for now there is this URL parameter. Enable this only if you need to
	 * override the client encoding when doing a copy.
	 * 
	 * - prepareThreshold = int Determine the number of PreparedStatement
	 * executions required before switching over to use server side prepared
	 * statements. The default is five, meaning start using server side prepared
	 * statements on the fifth execution of the same PreparedStatement object.
	 **/
	
}
